package com.csf.project.config;

import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;

import javax.servlet.Filter;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

import org.apache.shiro.authc.AuthenticationException;
import org.apache.shiro.authc.AuthenticationInfo;
import org.apache.shiro.authc.AuthenticationToken;
import org.apache.shiro.authc.IncorrectCredentialsException;
import org.apache.shiro.authc.SimpleAuthenticationInfo;
import org.apache.shiro.authc.UsernamePasswordToken;
import org.apache.shiro.authc.credential.CredentialsMatcher;
import org.apache.shiro.authc.credential.SimpleCredentialsMatcher;
import org.apache.shiro.authc.pam.AtLeastOneSuccessfulStrategy;
import org.apache.shiro.authc.pam.ModularRealmAuthenticator;
import org.apache.shiro.authz.AuthorizationException;
import org.apache.shiro.authz.AuthorizationInfo;
import org.apache.shiro.authz.ModularRealmAuthorizer;
import org.apache.shiro.authz.SimpleAuthorizationInfo;
import org.apache.shiro.authz.UnauthenticatedException;
import org.apache.shiro.authz.permission.WildcardPermissionResolver;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.realm.AuthorizingRealm;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.subject.PrincipalCollection;
import org.apache.shiro.web.filter.authc.AuthenticatingFilter;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.apache.shiro.web.util.WebUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpStatus;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseStatus;

import com.csf.project.service.AuthService;
import com.csf.project.service.SecurityService;
import com.csf.project.utils.CorsFilter;

@ControllerAdvice
@Configuration
public class ShiroConfig {

    @Autowired
    private SecurityService securityService;

    @Autowired
    private AuthService authService;

    @Autowired
    private CorsFilter corsFilter;

    public class AccessTokenFilter extends AuthenticatingFilter {

        static final String AUTHORIZATION_HEADER = "Authorization";
        static final String AUTHORIZATION_BEARER_PREFIX = "Bearer ";

        @Override
        protected AuthenticationToken createToken(ServletRequest request, ServletResponse response) throws Exception {
            String token = WebUtils.toHttp(request).getHeader(AUTHORIZATION_HEADER);
            if (StringUtils.startsWithIgnoreCase(token, AUTHORIZATION_BEARER_PREFIX))
                token = token.substring(AUTHORIZATION_BEARER_PREFIX.length());
            else
                token = "";
            return new UsernamePasswordToken(token, token);
        }

        @Override
        protected boolean onAccessDenied(ServletRequest request, ServletResponse response) throws Exception {
            if (isLoginRequest(request, response)) return true;
            if (executeLogin(request, response)) return true;
            WebUtils.toHttp(response).setStatus(HttpServletResponse.SC_UNAUTHORIZED);
            return false;
        }
    }

    public class ServiceRealm extends AuthorizingRealm {

        ServiceRealm() {
            setAuthenticationTokenClass(UsernamePasswordToken.class);
        }

        @Override
        protected AuthorizationInfo doGetAuthorizationInfo(PrincipalCollection principals) {
            String token = (String) getAvailablePrincipal(principals);
            String role = authService.decodeTokenRole(token);
            List<String> roleNames = Arrays.asList(role);
            List<String> permissions = securityService.getPermissions(roleNames);
            SimpleAuthorizationInfo info = new SimpleAuthorizationInfo(new HashSet<>(roleNames));
            info.setStringPermissions(new HashSet<>(permissions));
            return info;
        }

        @Override
        protected AuthenticationInfo doGetAuthenticationInfo(AuthenticationToken token) throws AuthenticationException {
            String accesstoken = (String)token.getPrincipal();
            String account = authService.decodeTokenAccount(accesstoken);
            securityService.loginSuccess(account);
            return new SimpleAuthenticationInfo(accesstoken, accesstoken.toCharArray(), getName());
        }
    }

    @Bean("shiroFilter")
    public ShiroFilterFactoryBean shiroFilterFactoryBean(SecurityManager securityManager) {
        ShiroFilterFactoryBean shiroFilterFactoryBean = new ShiroFilterFactoryBean();
        shiroFilterFactoryBean.setSecurityManager(securityManager);

        HashMap<String, Filter> map = new HashMap<>();
        map.put("accesstoken", new AccessTokenFilter());
        map.put("cors", corsFilter);
        shiroFilterFactoryBean.setFilters(map);
        Map<String, String> filterChainDefinitionMap = new LinkedHashMap<>();
        //放开全部接口，不走权限，测试可以放开
        //filterChainDefinitionMap.put("/**", "authc");

        filterChainDefinitionMap.put("/auth/login", "cors, noSessionCreation, anon");

        // filterChainDefinitionMap.put("/**", "cors, noSessionCreation, anon");
        shiroFilterFactoryBean.setFilterChainDefinitionMap(filterChainDefinitionMap);

        shiroFilterFactoryBean.setLoginUrl("/auth/login");

        return shiroFilterFactoryBean;
    }

    @Bean
    public DefaultWebSecurityManager securityManager() {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(serviceRealm());
        return securityManager;
    }

    @Bean
    public ServiceRealm serviceRealm() {
        ServiceRealm realm = new ServiceRealm();
        realm.setCredentialsMatcher(credentialsMatcher());
        return realm;
    }

    @Bean
    public CredentialsMatcher credentialsMatcher() {
        return new SimpleCredentialsMatcher();
    }

    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager){
        AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
        authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
        return authorizationAttributeSourceAdvisor;
    }

    @Bean
    public ModularRealmAuthenticator authenticator() {
        ModularRealmAuthenticator authenticator = new ModularRealmAuthenticator();
        authenticator.setAuthenticationStrategy(new AtLeastOneSuccessfulStrategy());
        return authenticator;
    }

    @Bean
    public ModularRealmAuthorizer authorizer() {
        ModularRealmAuthorizer authorizer = new ModularRealmAuthorizer();
        authorizer.setPermissionResolver(new WildcardPermissionResolver());
        return authorizer;
    }

    @Bean
    public DefaultWebSessionManager sessionManager() {
        return new DefaultWebSessionManager();
    }

//    @Bean
//    public AccessTokenFilter accessTokenFilter() {
//        return new AccessTokenFilter();
//    }

    @ExceptionHandler(UnauthenticatedException.class)
    @ResponseStatus(HttpStatus.UNAUTHORIZED)
    public void handleException(UnauthenticatedException e) {
    }

    @ExceptionHandler(AuthorizationException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public void handleException(AuthorizationException e) {
    }

    @ExceptionHandler(IncorrectCredentialsException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public void handleException(IncorrectCredentialsException e) {
    }

    @ExceptionHandler(AuthenticationException.class)
    @ResponseStatus(HttpStatus.FORBIDDEN)
    public void handleException(AuthenticationException e) {
    }

}
